﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using System.Security.Policy;
using System.Globalization;

namespace PluggableModulesInterface
{
    public static class PluggableManage
    {
        private static object _lock = new object();
        private static Dictionary<string, IPluggableModule> _ListOfModule = new Dictionary<string, IPluggableModule>();
        private static Dictionary<string, Stream> _ListOfStream = new Dictionary<string, Stream>();

        public static Dictionary<string, IPluggableModule> GetListOfModule()
        {
            return _ListOfModule;
        }
        public static void RemoveModuleInstance(string key)
        {
            if (_ListOfModule.ContainsKey(key))
            {
                ((IPluggableModule)_ListOfModule[key]).Stop();
                ((IPluggableModule)_ListOfModule[key]).Dispose();
                _ListOfModule.Remove(key);
            }
        }
        public static void RemoveStream(string key)
        {
            if (_ListOfStream.ContainsKey(key))
            {
                ((Stream)_ListOfStream[key]).Close();
                _ListOfStream.Remove(key);
            }
        }
        //public static bool UnloadAssembly(string filePath)
        //{
        //    try
        //    {
        //        string streamKey = Path.GetFileName(filePath);
        //        foreach (string key in _ListOfModule.Keys)
        //        {
        //            string[] s = key.Split('|');
        //            if (s.Length >= 2)
        //            {
        //                if (s[0] == streamKey)
        //                {
        //                    PluggableManage.RemoveModuleInstance(key);
        //                    PluggableManage.RemoveStream(streamKey);
        //                    return true;
        //                }
        //            }
        //        }
        //        return true;
        //    }
        //    catch (Exception)
        //    {
        //        return false;
        //    }
        //}
        public static bool UnloadAssemblyOfStoppedModule(string filePath)
        {
            try
            {
                string streamKey = Path.GetFileName(filePath);
                foreach (string key in _ListOfModule.Keys)
                {
                    string[] s = key.Split('|');
                    if (s.Length >= 2)
                    {
                        if (s[0] == streamKey)
                        {
                            if (!_ListOfModule[key].Running)
                            {
                                PluggableManage.RemoveModuleInstance(key);
                                PluggableManage.RemoveStream(streamKey);
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }
                }
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public static void Init(string path)
        {
            try
            {
                //RunWatcher();
                LoadExistingModules(path);
            }
            catch (Exception)
            {
                throw;
            }
        }

        public static void Start()
        {
            lock (_lock)
            {
                foreach (var item in _ListOfModule)
                {
                    if (item.Value != null)
                    {
                        item.Value.Stop();
                        item.Value.Start();
                    }
                }
            }
        }

        public static void Stop()
        {
            lock (_lock)
            {
                foreach (var item in _ListOfModule)
                {
                    if (item.Value != null)
                        item.Value.Stop();
                }
            }
        }

        //Hàm theo dõi sự thay đổi file trong thư mục chứa dll
        private static void RunWatcher()
        {
            try
            {
                FileSystemWatcher modulesWatcher = new FileSystemWatcher();
                modulesWatcher.Path = Settings.AppSetting.AppModulesPath;
                //modulesWatcher.Filter = "*.dll";
                //modulesWatcher.NotifyFilter = NotifyFilters.FileName;
                modulesWatcher.Created += new FileSystemEventHandler(OnCreated);
                modulesWatcher.Deleted += new FileSystemEventHandler(OnDeleted);
                // Begin watching.
                modulesWatcher.EnableRaisingEvents = true;
            }
            catch (DirectoryNotFoundException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        //Sự kiện xảy ra khi có file được thêm vào thư mục
        private static void OnCreated(object source, FileSystemEventArgs e)
        {
            //LoadAssembly(e.FullPath);
            string[] file = Directory.GetFiles(e.FullPath, "*.dll");
            foreach (string fileName in file)
            {
                LoadAssembly(fileName);
            }
        }

        //Sự kiện xảy ra khi có file được xóa khỏi thư mục
        private static void OnDeleted(object source, FileSystemEventArgs e)
        {
            //ReloadModules();
        }

        public static IPluggableModule CreateModuleInstance(Assembly assembly, out string typeFullName)
        {
            foreach (Type type in assembly.GetTypes())
            {
                Type moduleInterface = type.GetInterface("PluggableModulesInterface.IPluggableModule", false);
                if (moduleInterface != null)
                {
                    if (!_ListOfModule.ContainsKey(type.FullName))
                    {
                        IPluggableModule moduleInstance = (IPluggableModule)Activator.CreateInstance(type);
                        typeFullName = type.FullName;
                        return moduleInstance;
                    }
                }
            }
            typeFullName = "";
            return null;
        }

        public static KeyValuePair<string, IPluggableModule> LoadAssembly(string filePath)
        {
            try
            {
                // Use stream to lock dll file, not allow other program modifies it
                Stream stream = new FileStream(filePath, FileMode.Open);
                string streamKey = Path.GetFileName(filePath);
                if (!_ListOfStream.ContainsKey(streamKey)) _ListOfStream.Add(streamKey, stream);
                // Load assembly from byte array
                FileInfo fileInfo = new FileInfo(filePath);
                byte[] arrbyte = new byte[fileInfo.Length];
                stream.Read(arrbyte, 0, arrbyte.Length);
                Assembly assembly = Assembly.Load(arrbyte);
                // Create instance
                string typeFullName = "";
                IPluggableModule moduleInstance = CreateModuleInstance(assembly, out typeFullName);
                if (moduleInstance != null && !_ListOfModule.ContainsKey(typeFullName))
                {
                    moduleInstance.AssemblyLocation = filePath;
                    string key = streamKey + "|" + typeFullName;
                    _ListOfModule.Add(key, moduleInstance);
                    return new KeyValuePair<string, IPluggableModule>(key, moduleInstance);
                }
                return new KeyValuePair<string,IPluggableModule>();
            }
            catch (Exception)
            {
                throw;
            }
        }

        public static void LoadExistingModules(string folderPath)
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(folderPath))
                {
                    string[] filePaths = Directory.GetFiles(folderPath, "*.dll");
                    foreach (string filePath in filePaths)
                    {
                        if (UnloadAssemblyOfStoppedModule(filePath))
                        {
                            LoadAssembly(filePath);
                        }
                    }
                    string[] subFolderPaths = Directory.GetDirectories(folderPath);
                    foreach (string subFolderPath in subFolderPaths)
                    {
                        LoadExistingModules(subFolderPath);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}